<?php

namespace zelda;

/**
 * 分页处理
 * 本类封装了一个简单的分页，可以设置各种样式
 * 改编自作者ustb80的代码
 * Class Pagination
 * @package zelda
 * @author ustb80
 */
class Pagination
{

    use UrlRenewBuildTrait;

    //实例
    protected static $instance = null;

    //基本参数
    protected $totalNum = 0; //总数量
    protected $perpage = 10; //每页数量
    protected $currentPage = 1; //当前页码
    protected $delta = 2; //增量，即当前页前后可点击的数量
    protected $etcNum = 1; //省略数量
    protected $baseUrl = ''; //地址规则，首页||分页，示例：/xz/list.html||/xz/list/p#page#.html
    protected $pageParam = 'page';//分页参数

    //格式
    protected $statFormat = "共有%d条信息"; //统计格式，示例：共有%d条信息
    protected $regionFormat = "当前第%d页"; //当前页范围格式，示例：当前第%d页

    //省略数量的容器标签
    protected $etcTagOpen = '<span>';
    protected $etcTagClose = '</span>';
    protected $etcTagClass = 'etc';

    //开关项
    protected $isShowStat = true; //是否显示统计
    protected $isShowRegion = true; //是否显示当前页范围
    protected $isShowBeginEnd = true; //是否显示首尾链接
    protected $isShowLinkList = true; //是否显示链接列表
    protected $isShowBeginEtc = true; //是否显示前端省略
    protected $isShowEndEtc = true; //是否显示后端省略
    protected $isShowJumpTxt = true; //是否显示快速跳转
    protected $isShowJumpJs = true; //是否开启跳转JS

    //文本
    protected $firstPageTxt = "首页";
    protected $prePageTxt = "上一页";
    protected $nextPageTxt = "下一页";
    protected $lastPageTxt = "尾页";
    protected $jumpTxt = "跳转到";//快速跳转文本

    //容器标签相关
    protected $containerTagOpen = "<ul>";
    protected $containerTagClose = "</ul>";
    protected $containerTagClass = "";//容器标签class

    //节点
    protected $itemTagOpen = "<li>";
    protected $itemTagClose = "</li>";
    protected $itemTagClassStat = "all";//节点统计class
    protected $itemTagClassRegion = "";//节点当前页范围class
    protected $itemTagClassLink = "";//节点class
    protected $itemTagClassNolink = "";//节点无链接的class
    protected $itemTagClassCurrent = "";//节点选中class
    protected $itemTagClassInput = "";//节点input class

    //文本节点
    protected $txtTagOpen = "<span>";
    protected $txtTagClose = "</span>";
    protected $txtTagClass = "";//文本节点class
    protected $txtTagClassStat = "stat";//统计部分class
    protected $txtTagClassRegion = "region";//页码范围class
    protected $txtTagClassNolink = "9632";
    protected $txtTagClassCurrent = "current";//当前页选中class

    //a链接样式
    protected $linkClassNormal = "";

    //跳转输入框样式
    protected $jumpInputClass = "";
    protected $jumpInputSize = "5";//跳转框的尺寸属性
    protected $jumpInputId = "__jumpTo";//跳转框的ID属性

    /**
     * 构造函数
     * @param array $params 初始化参数
     */
    public function __construct(array $params = [])
    {
        $this->init($params);
    }

    /**
     * 初始化视图
     * 单例模式
     * @param array $params
     * @return null|Pagination
     */
    public static function instance(array $params = [])
    {
        if (is_null(self::$instance)) {
            self::$instance = new self($params);
        }
        return self::$instance;
    }

    /**
     * 分页参数初始化
     * @param array $params
     * @return $this
     */
    public function init(array $params)
    {
        foreach ($params as $key => $val) {
            if (isset($this->$key)) {
                $this->$key = $val;
            }
        }
        return $this;
    }

    /**
     * 取得当前页码
     * @return number
     */
    public function getCurrentPage()
    {
        $pageCount = $this->getPageCount();
        if ($this->currentPage > $pageCount) {
            $this->currentPage = $pageCount;
        }
        return $this->currentPage;
    }

    /**
     * 获得总分页数
     * @return int
     */
    public function getPageCount()
    {
        if (empty($this->totalNum) || empty($this->perpage)) {
            return 0;
        }
        return ceil($this->totalNum / $this->perpage);
    }

    /**
     * 创建分页
     * @return string
     */
    public function create()
    {
        //总页码数
        $pageCount = $this->getPageCount();
        //当前页码
        $currentPage = $this->getCurrentPage();

        //总页数小于等于0情况下不启用分页
        if ($pageCount <= 0) {
            return '';
        }

        //前后页码 34[5]67，当前是5，则第一页是5-2=3,最后一页是5+2 = 7
        $pageFirst = $currentPage - $this->delta;
        $pageLast = $currentPage + $this->delta;

        //1[2]34
        if ($pageFirst < 1) {
            $pageFirst = 1;
            $pageLast = $pageFirst + $this->delta * 2;
        }

        //计算前后页码上下限
        $pageEnd = $pageCount;
        if ($this->isShowEndEtc === true) {
            if ($pageLast > $pageCount - $this->etcNum) {
                $pageEnd = $pageCount - $this->etcNum;
            }
        }

        if ($pageLast > $pageEnd) {
            $pageLast = $pageEnd;
            $pageFirst = $pageLast - $this->delta * 2;
            if ($pageFirst < 1) {
                $pageFirst = 1;
            }
        }

        //是否显示前端省略
        if ($this->isShowBeginEtc === true) {
            if ($pageFirst <= $this->etcNum) {
                $pageFirst = 1;
            }
        }

        //计算当前页数据行范围
        $rowBegin = ($currentPage - 1) * $this->perpage + 1;
        $rowEnd = $rowBegin + $this->perpage - 1;
        if ($rowEnd > $this->totalNum) {
            $rowEnd = $this->totalNum;
        }

        //组装
        $output = '';

        //显示快速跳转+开启跳转JS
        if ($this->isShowJumpTxt === true && $this->isShowJumpJs === true) {
            $output .= $this->_buildJumpJs();
        }
        $output .= $this->buildTag($this->containerTagOpen, $this->containerTagClass);
        $output .= $this->_buildStatPart(); //统计数
        $output .= $this->_buildRegionPart($rowBegin, $rowEnd); //范围数

        //当前页大于1时，第一页，首页可用
        if ($currentPage > 1) {
            if ($this->isShowBeginEnd === true) {
                $output .= $this->_buildFirstLinkPart();
            }
            $output .= $this->_buildPreLinkPart();
        } else {
            //如果是第一页，不调用【首页标识文本】
            if ($this->isShowBeginEnd === true && $currentPage != 1) {
                $output .= $this->_buildFirstTxtPart();
            }
            //如果是第一页，不调用【上一页标识文本】
            if ($currentPage != 1) {
                $output .= $this->_buildPreTxtPart();
            }
        }

        //是否显示链接列表
        if ($this->isShowLinkList === true) {
            if ($this->isShowBeginEtc === true) {
                $output .= $this->_buildBeginEtc($pageFirst);
            }
            $output .= $this->_buildLinks($pageFirst, $pageLast);
            if ($this->isShowEndEtc === true) {
                $output .= $this->_buildEndEtc($pageLast, $pageCount);
            }
        }

        //尾页处理，如果是最后一页了，那尾页和后一页不可用
        if ($currentPage < $pageCount) {
            $output .= $this->_buildNextLinkPart();
            if ($this->isShowBeginEnd === true) {
                $output .= $this->_buildLastLinkPart($pageCount);
            }
        } else {
            //如果是尾页，不调用【下一页链接】
            if ($currentPage != $pageCount) {
                $output .= $this->_buildNextTxtPart();
            }
            //如果是尾页，不调用【尾页链接】
            if ($this->isShowBeginEnd === true && $currentPage != $pageCount) {
                $output .= $this->_buildLastTxtPart();
            }
        }

        $output .= $this->_buildJumpInput();
        $output .= $this->containerTagClose;

        return $output;
    }

    /**
     * 修改器 设置数据参数值
     * @access public
     * @param string $name 属性名
     * @param mixed $value 属性值
     * @return $this
     */
    public function setAttr($name, $value)
    {
        if (isset($this->$name)) {
            $this->$name = $value;
        }
        return $this;
    }

    /**
     * 创建普通元素
     * @param string $tag 标签
     * @param string $class 样式
     * @return boolean mixed
     */
    protected function buildTag($tag, $class = '')
    {
        if (!preg_match('#^<[^<>]+>$#i', $tag)) {
            return '';
        }
        if ($class != '') {
            $tag = str_replace('>', ' class="' . $class . '">', $tag);
        }
        return $tag;
    }

    /**
     * 创建链接
     *
     * @param string $href 链接地址
     * @param string $txt 文本
     * @param string $class 样式
     * @return string
     */
    protected function buildLink($href, $txt, $class = '')
    {
        $class_str = '';
        if ($class != '') {
            $class_str = ' class="' . $class . '"';
        }
        $tag = '<a href="' . $href . '"' . $class_str . '>' . $txt . '</a>';
        return $tag;
    }

    /**
     * 创建跳转输入框
     * @return string
     */
    protected function _buildJumpInput()
    {
        $output = '';
        if ($this->isShowJumpTxt === true) {
            $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassInput);
            $jumpInputClass = '';
            if ($this->jumpInputClass != '') {
                $jumpInputClass = ' class="' . $this->jumpInputClass . '"';
            }
            if ($this->isShowJumpJs === true) {
                $output .= $this->jumpTxt . '<input type="text" size="' . $this->jumpInputSize . '" id="' . $this->jumpInputId . '" value="' . $this->getCurrentPage() . '"' . $jumpInputClass . ' onkeydown="return pageJump(event);"/>';
            } else {
                $output .= $this->jumpTxt . '<input type="text" size="' . $this->jumpInputSize . '" id="' . $this->jumpInputId . '" value="' . $this->getCurrentPage() . '"' . $jumpInputClass . '/>';
            }
            $output .= $this->itemTagClose;
        }
        $output .= "\n";
        return $output;
    }

    /**
     * 创建跳转js
     * @return string
     */
    protected function _buildJumpJs()
    {
        //地址是成对显示，第一个为首页地址模板，第二个为分页地址模板
        $urlArr = array_filter(explode('||', $this->baseUrl, 2));
        if (empty($urlArr)) {
            //没有设置地址规则
            //当前完整地址
            $realUrl = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
            $urlArr = [self::urlBuild($realUrl, [$this->pageParam => 1]), self::urlBuild($realUrl, [$this->pageParam => '#page#'])];
            $urlArr[1] = str_replace('%23page%23', '#page#', $urlArr[1]);
        }
        //有设置地址规则
        $scriptUrl = 'if(pageTo===1){var url ="' . $urlArr[0] . '";}else{var pageUrl ="' . $urlArr[1] . '"; var url =pageUrl.replace("#page#",pageTo,pageUrl);}' . "\n";
        $output = '<script>' . "\n";
        $output .= 'function pageJump(e){' . "\n";;
        $output .= 'var currKey = e.keyCode||e.which||e.charCode;' . "\n";
        $output .= 'if(currKey == 13){' . "\n";
        $output .= 'var objInput = document.getElementById("' . $this->jumpInputId . '");' . "\n";
        $output .= 'var baseUrl = "' . $this->baseUrl . '";' . "\n";
        $output .= 'var pageTo = objInput.value;' . "\n";
        $output .= 'pageTo = parseInt(pageTo);' . "\n";
        $output .= $scriptUrl;
        $output .= 'window.location.href = url;' . "\n";
        $output .= '}}' . "\n";
        $output .= '</script>';
        $output .= "\n";
        return $output;
    }

    /**
     * 创建统计部分
     * @return string
     */
    protected function _buildStatPart()
    {
        $output = '';
        //显示创建统计部分且统计格式不为空
        if ($this->isShowStat === true && !empty($this->statFormat)) {
            $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassStat);
            $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassStat);
            $output .= sprintf($this->statFormat, $this->totalNum);
            $output .= $this->txtTagClose;
            $output .= $this->itemTagClose;
        }
        $output .= "\n";
        return $output;
    }

    /**
     * 创建范围显示部分
     * @param int $rowBegin 起始行
     * @param int $rowEnd 结束行
     * @return string
     */
    protected function _buildRegionPart($rowBegin, $rowEnd)
    {
        $output = '';
        //显示创建范围显示部分且当前页范围格式不为空
        if ($this->isShowRegion === true && !empty($this->regionFormat)) {
            $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassRegion);
            $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassRegion);
            $output .= sprintf($this->regionFormat, $rowBegin, $rowEnd);
            $output .= $this->txtTagClose;
            $output .= $this->itemTagClose;
        }
        $output .= "\n";
        return $output;
    }

    /**
     * 创建链接
     * @param int $pageFirst
     * @param int $pageLast
     * @return string
     */
    protected function _buildLinks($pageFirst, $pageLast)
    {
        $output = '';
        for (; $pageFirst <= $pageLast; $pageFirst++) {
            if ($pageFirst == $this->getCurrentPage()) {
                $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassCurrent);
                $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassCurrent);
                $output .= $pageFirst;
                $output .= $this->txtTagClose;
                $output .= $this->itemTagClose;
            } else {
                $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
                $output .= $this->buildLink($this->_buildPageLink($pageFirst), $pageFirst, $this->linkClassNormal);
                $output .= $this->itemTagClose;
            }
            $output .= "\n";
        }

        return $output;
    }

    /**
     * 拼接分页链接例如：/xz/list.html||/xz/list/p#page#.html
     * @param int $pageNum 页码号
     * @return string
     */
    protected function _buildPageLink($pageNum)
    {
        if (!empty($this->baseUrl)) {
            //带有自定义分页规则的分页链接模式
            $urlArr = array_filter(explode('||', $this->baseUrl, 2));
            if ($pageNum == 1) {
                return $urlArr[0];
            } else {
                return str_replace(['#page#', '%23page%23'], $pageNum, $urlArr[1]);
            }
        } else {
            //无分页规则的分页链接模式
            //当前完整地址
            $realUrl = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
            return self::urlBuild($realUrl, [$this->pageParam => $pageNum]);
        }
    }

    /**
     * 创建前端省略
     * @param int $pageFirst 起始数
     * @return string
     */
    protected function _buildBeginEtc($pageFirst)
    {
        $output = '';
        if ($pageFirst > $this->etcNum) {
            //循环出分页列表
            $pageBegin = 1;
            for (; $pageBegin <= $this->etcNum; $pageBegin++) {
                if ($pageBegin == $this->getCurrentPage()) {
                    $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassCurrent);
                    $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassCurrent);
                    $output .= $pageBegin;
                    $output .= $this->txtTagClose;
                    $output .= $this->itemTagClose;
                } else {
                    $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
                    $output .= $this->buildLink($this->_buildPageLink($pageBegin), $pageBegin, $this->linkClassNormal);
                    $output .= $this->itemTagClose;
                }
                $output .= "\n";
            }
        }

        if ($pageFirst > $this->etcNum + 1) {
            $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassNolink);
            $output .= $this->buildTag($this->etcTagOpen, $this->etcTagClass);
            $output .= '...';
            $output .= $this->etcTagClose;
            $output .= $this->itemTagClose;
            $output .= "\n";
        }

        return $output;
    }

    /**
     * 创建后端省略
     * @param int $pageLast
     * @param int $pageCount
     * @return string
     */
    protected function _buildEndEtc($pageLast, $pageCount)
    {
        $output = '';
        if ($pageLast < $pageCount - $this->etcNum) {
            $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassNolink);
            $output .= $this->buildTag($this->etcTagOpen, $this->etcTagClass);
            $output .= '...';
            $output .= $this->etcTagClose;
            $output .= $this->itemTagClose;
            $output .= "\n";
        }

        if ($pageLast < $pageCount - $this->etcNum + $this->delta) {
            //循环出分页列表
            $pageBegin = $pageCount - $this->etcNum + 1;
            for (; $pageBegin <= $pageCount; $pageBegin++) {
                if ($pageBegin == $this->getCurrentPage()) {
                    $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassCurrent);
                    $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassCurrent);
                    $output .= $pageBegin;
                    $output .= $this->txtTagClose;
                    $output .= $this->itemTagClose;
                } else {
                    $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
                    $output .= $this->buildLink($this->_buildPageLink($pageBegin), $pageBegin, $this->linkClassNormal);
                    $output .= $this->itemTagClose;
                }
                $output .= "\n";
            }
        }
        return $output;
    }

    /**
     * 创建第一页链接
     * @return string
     */
    protected function _buildFirstLinkPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
        $output .= $this->buildLink($this->_buildPageLink(1), $this->firstPageTxt, $this->linkClassNormal);
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建最后一页链接
     * @param int $pageCount 总页数
     * @return string
     */
    protected function _buildLastLinkPart($pageCount)
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
        $output .= $this->buildLink($this->_buildPageLink($pageCount), $this->lastPageTxt, $this->linkClassNormal);
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建前一页链接
     * @return string
     */
    protected function _buildPreLinkPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
        $output .= $this->buildLink($this->_buildPageLink($this->getCurrentPage() - 1), $this->prePageTxt, $this->linkClassNormal);
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建后一页链接
     * @return string
     */
    protected function _buildNextLinkPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassLink);
        $output .= $this->buildLink($this->_buildPageLink($this->getCurrentPage() + 1), $this->nextPageTxt, $this->linkClassNormal);
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建首页无链接文本
     * @return string
     */
    protected function _buildFirstTxtPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassNolink);
        $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassNolink);
        $output .= $this->firstPageTxt;
        $output .= $this->txtTagClose;
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建尾页无链接文本
     * @return string
     */
    protected function _buildLastTxtPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassNolink);
        $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassNolink);
        $output .= $this->lastPageTxt;
        $output .= $this->txtTagClose;
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建前一页文本
     * @return string
     */
    protected function _buildPreTxtPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassNolink);
        $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassNolink);
        $output .= $this->prePageTxt;
        $output .= $this->txtTagClose;
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

    /**
     * 创建后一页文本
     * @return string
     */
    protected function _buildNextTxtPart()
    {
        $output = '';
        $output .= $this->buildTag($this->itemTagOpen, $this->itemTagClassNolink);
        $output .= $this->buildTag($this->txtTagOpen, $this->txtTagClassNolink);
        $output .= $this->nextPageTxt;
        $output .= $this->txtTagClose;
        $output .= $this->itemTagClose;
        $output .= "\n";
        return $output;
    }

}